package com.freewater.library.lending.checkedoutsheet.eventhandler;

import com.freewater.library.catalogue.model.Book;
import com.freewater.library.catalogue.model.BookRepository;
import com.freewater.library.common.domain.DomainException;
import com.freewater.library.lending.book.model.BookInstance;
import com.freewater.library.lending.book.model.BookInstanceRepository;
import com.freewater.library.lending.book.model.event.BookInstanceCheckedOutEvent;
import com.freewater.library.lending.book.model.event.BookInstanceCheckedOutOverdueEvent;
import com.freewater.library.lending.book.model.event.BookInstanceLostEvent;
import com.freewater.library.lending.book.model.event.BookInstanceReturnedEvent;
import com.freewater.library.lending.checkedoutsheet.infrastructure.dao.CheckedOutRecordDao;
import com.freewater.library.lending.checkedoutsheet.model.CheckedOutRecord;
import com.freewater.library.lending.reader.model.Reader;
import com.freewater.library.lending.reader.model.ReaderRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.Optional;

@Slf4j
@Component
@RequiredArgsConstructor
public class CheckedOutRecordConsumer {

    private final CheckedOutRecordDao checkedOutRecordDao;
    private final BookRepository bookRepository;
    private final BookInstanceRepository bookInstanceRepository;
    private final ReaderRepository readerRepository;

    @Transactional
    @EventListener(BookInstanceCheckedOutEvent.class)
    public void onCheckedOutEvent(BookInstanceCheckedOutEvent event) {
        String qrCode = event.getQrCode();
        String isbn = event.getIsbn();
        Long checkedOutBy = event.getReaderId();
        LocalDateTime checkedOutAt = event.getOccurAt();
        Reader reader = this.requireReader(checkedOutBy);
        Book book = this.requireBook(isbn);
        BookInstance bookInstance = this.requireBookInstance(qrCode);
        // 读者最近一次借出此书的记录
        CheckedOutRecord.Unique unique = new CheckedOutRecord.Unique(qrCode, checkedOutBy, checkedOutAt);
        boolean exists = this.checkedOutRecordDao.existsByUnique(unique);
        CheckedOutRecord checkedOutRecord =
                new CheckedOutRecord(book, bookInstance, reader, checkedOutAt);
        // 没有记录或者不是相同的记录则新增
        if (!exists) {
            this.checkedOutRecordDao.save(checkedOutRecord);
        } else {
            log.warn("重复借出事件: {}", event);
        }
    }

    @Transactional
    @EventListener(BookInstanceCheckedOutOverdueEvent.class)
    public void onCheckedOutOverdue(BookInstanceCheckedOutOverdueEvent event) {
        CheckedOutRecord.Unique unique = new CheckedOutRecord.Unique(event.getQrCode(),
                event.getReaderId(), event.getCheckedOutAt());
        Optional<CheckedOutRecord> recordOpt = this.checkedOutRecordDao.findByUnique(unique);
        if (recordOpt.isPresent()) {
            CheckedOutRecord record = recordOpt.get();
            record.overdue();
            this.checkedOutRecordDao.save(record);
        } else {
            log.warn("重复的借出预期事件：{}", event);
        }
    }

    @Transactional
    @EventListener(BookInstanceReturnedEvent.class)
    public void onReturnedBackEvent(BookInstanceReturnedEvent event) {
        LocalDateTime returnBackTime = event.getOccurAt();
        CheckedOutRecord.Unique unique = new CheckedOutRecord.Unique(event.getQrCode(),
                event.getReaderId(), event.getCheckedOutAt());
        Optional<CheckedOutRecord> recordOpt = this.checkedOutRecordDao.findByUnique(unique);
        if (recordOpt.isPresent()) {
            CheckedOutRecord record = recordOpt.get();
            record.returnBack(returnBackTime);
            this.checkedOutRecordDao.save(record);
        } else {
            log.warn("重复的归还事件：{}", event);
        }
    }

    @Transactional
    @EventListener(BookInstanceLostEvent.class)
    public void onSubmitLostEvent(BookInstanceLostEvent event) {
        LocalDateTime lostTime = event.getOccurAt();
        CheckedOutRecord.Unique unique = new CheckedOutRecord.Unique(event.getQrCode(),
                event.getReaderId(), event.getCheckedOutAt());
        Optional<CheckedOutRecord> recordOpt = this.checkedOutRecordDao.findByUnique(unique);
        if (recordOpt.isPresent()) {
            CheckedOutRecord record = recordOpt.get();
            record.lost(lostTime);
            this.checkedOutRecordDao.save(record);
        } else {
            log.warn("重复的遗失事件：{}", event);
        }
    }

    private Book requireBook(String isbn) {
        Optional<Book> bookOpt = this.bookRepository.findById(isbn);
        return bookOpt.orElseThrow(() -> new DomainException("无效的书"));
    }

    private BookInstance requireBookInstance(String qrCode) {
        Optional<BookInstance> bookOpt = this.bookInstanceRepository.findById(qrCode);
        return bookOpt.orElseThrow(() -> new DomainException("无效的书"));
    }

    private Reader requireReader(Long readerId) {
        Optional<Reader> readerOpt = this.readerRepository.findById(readerId);
        return readerOpt.orElseThrow(() -> new DomainException("无效的读者"));
    }
}
